package com.lcm.openapi;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import com.lcm.openapi.config.GenerateConfig;
import com.lcm.openapi.core.api.Api;
import com.lcm.openapi.core.api.ApiMethod;
import com.lcm.openapi.core.model.ApiModel;
import com.lcm.openapi.core.model.ApiModelField;
import com.lcm.openapi.generator.NameGenerator;
import com.lcm.openapi.resolver.DataSourceResolver;
import com.lcm.openapi.resolver.URIResolver;
import com.lcm.openapi.scanner.ApiMethodScanner;
import com.lcm.openapi.scanner.ApiModelScanner;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class CodeGenerateProcessor {

    public static final String IMPORT_PREFIX = "import ";

    private final DataSourceResolver dataSourceResolver;
    private final ApiModelScanner apiModelScanner;
    private final ApiMethodScanner apiMethodScanner;
    private final GenerateConfig generateConfig;

    public CodeGenerateProcessor(DataSourceResolver dataSourceResolver, GenerateConfig generateConfig) {
        this.dataSourceResolver = dataSourceResolver;
        this.generateConfig = generateConfig;
        apiModelScanner = new ApiModelScanner(generateConfig);
        apiMethodScanner = new ApiMethodScanner(generateConfig);
    }

    public static void main(String[] args) {
        String url = "http://127.0.0.1:4523/export/openapi/2?version=3.0";
        String rootPath = "C:\\Users\\EDY\\Desktop\\lcm\\workspace\\demo\\OpenApiGenerator\\src\\main\\java\\";
        GenerateConfig generateConfig = new GenerateConfig();
        generateConfig.setRootPath(rootPath);
        CodeGenerateProcessor processor = new CodeGenerateProcessor(new URIResolver(), generateConfig);
        processor.execute(url);
    }

    public void execute(String resource) {
        //读取资源文件
        Reader reader = dataSourceResolver.getReader(resource);
        //读取内容转换成json,关闭ref循环读取
        JSONObject dataJson = JSON.parseObject(IoUtil.read(reader), Feature.DisableCircularReferenceDetect);
        //解析model数据
        List<ApiModel> apiModelList = apiModelScanner.scan(dataJson);
        //解析apiMethod数据
        List<ApiMethod> apiMethodList = apiMethodScanner.scan(dataJson);
        //合并api数据
        List<Api> apiList = this.combineApi(apiModelList, apiMethodList);
        //生成模板文件
        this.generateTempFile(apiList);
    }

    private void generateTempFile(List<Api> list) {
        //初始化模板引擎
        MustacheFactory mf = new DefaultMustacheFactory();
        Mustache apiMustache = mf.compile(generateConfig.getApiMustachePath());
        Mustache modelMustache = mf.compile(generateConfig.getModelMustachePath());
        //根目录
        String rootPath = generateConfig.getRootPath();
        //初始化模块文件夹
        String modelPackPath = String.join(File.separator, generateConfig.getModelPackage().split("\\."));
        //初始化dto文件夹
        String dtoPackagePath = this.initDTOPath(modelPackPath);
        //初始化vo文件夹
        String voPackagePath = this.initVOPath(modelPackPath);
        //初始化接口文件夹
        String apiPackagePath = rootPath + String.join(File.separator, generateConfig.getApiPackage().split("\\.")) + File.separator;
        FileUtil.mkdir(apiPackagePath);
        //生成模板文件
        list.forEach(a -> {
            //生成接口文件
            this.generateFile(apiPackagePath, a.getApiName(), apiMustache, a);
            //生成dto模型文件
            List<ApiModel> requestModel = a.getRequestModel();
            requestModel.forEach(reqModel -> {
                if (CharSequenceUtil.isNotEmpty(generateConfig.getRequestPackageName())) {
                    reqModel.setPackageName(reqModel.getPackageName() + "." + generateConfig.getRequestPackageName());
                }
                this.generateFile(dtoPackagePath, reqModel.getModelName(), modelMustache, reqModel);
            });
            //生成vo模型文件
            List<ApiModel> responseModel = a.getResponseModel();
            responseModel.forEach(resModel -> {
                if (CharSequenceUtil.isNotEmpty(generateConfig.getResponsePackageName())) {
                    resModel.setPackageName(resModel.getPackageName() + "." + generateConfig.getResponsePackageName());
                }
                this.generateFile(voPackagePath, resModel.getModelName(), modelMustache, resModel);
            });
        });
    }

    private String initDTOPath(String modelPackPath) {
        String dtoPackagePath = generateConfig.getRootPath() + modelPackPath + File.separator;
        if (CharSequenceUtil.isNotEmpty(generateConfig.getRequestPackageName())) {
            dtoPackagePath = dtoPackagePath + generateConfig.getRequestPackageName() + File.separator;
        }
        FileUtil.mkdir(dtoPackagePath);
        return dtoPackagePath;
    }

    private void generateFile(String apiPackagePath, String fileName, Mustache mustache, Object obj) {
        //生成api模板文件
        String modelFile = apiPackagePath + fileName + ".java";
        try(FileWriter fw = new FileWriter(modelFile);) {
            log.info("开始生成api模板文件:{}", modelFile);
            FileUtil.touch(modelFile);
            mustache.execute(fw, obj).flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String initVOPath(String modelPackPath) {
        String voPackagePath = generateConfig.getRootPath() + modelPackPath + File.separator;
        if (CharSequenceUtil.isNotEmpty(generateConfig.getResponsePackageName())) {
            voPackagePath = voPackagePath + generateConfig.getResponsePackageName() + File.separator;
        }
        FileUtil.mkdir(voPackagePath);
        return voPackagePath;
    }

    //合并api数据
    private List<Api> combineApi(List<ApiModel> apiModelList, List<ApiMethod> apiMethodList) {
        //apiModelList根据名称转换成map
        Map<String, ApiModel> modelMap = apiModelList.stream().collect(Collectors.toMap(ApiModel::getModelName, a -> a, (a, b) -> a));
        //apiMethod根据tags分组
        Map<String, List<ApiMethod>> map = apiMethodList.stream().collect(Collectors.groupingBy(ApiMethod::getTags));
        //名称生成器
        NameGenerator nameGenerator = generateConfig.getNameGenerator();
        //最终的api
        List<Api> list = new ArrayList<>();
        map.forEach((k, v) -> {
            Api api = new Api();
            //提取公共的url
            List<String> urlList = v.stream().map(ApiMethod::getUrl).collect(Collectors.toList());
            //找到最短的url
            String shortestUrl = Collections.min(urlList, Comparator.comparing(String::length));
            //判断是否所有url都包含
            if (!this.isAllContains(urlList, shortestUrl)) {
                while (shortestUrl.contains("/")) {
                    shortestUrl = shortestUrl.substring(0, shortestUrl.lastIndexOf("/"));
                    if (this.isAllContains(urlList, shortestUrl)) {
                        break;
                    }
                }
            }
            api.setPackageName(generateConfig.getApiPackage());
            api.setBaseUrl(shortestUrl);
            api.setTagName(k);
            api.setApiName(nameGenerator.generateApiName(api));
            api.setMethods(v);
            //引入包
            this.addImports(api);

            //遍历apiModel
            v.forEach(apiMethod -> {
                //替换url
                String url = apiMethod.getUrl();
                url = url.substring(url.indexOf(api.getBaseUrl()) + api.getBaseUrl().length());
                if(CharSequenceUtil.isEmpty(url)){
                    url = "/";
                }
                apiMethod.setUrl(url);
                //引入的包
                api.getImports().addAll(apiMethod.getImports());
                api.getImports().remove(null);
                //关联的请求对象
                this.addRequestModel(api, apiMethod, modelMap);
                //关联的返回对象
                this.addResponseModel(api, apiMethod, modelMap);
            });
            list.add(api);
        });
        return list;
    }

    private void addImports(Api api) {
        //通用返回引入包
        Set<String> imports = api.getImports();
        String commonResponsePackage = generateConfig.getCommonResponsePackage();
        if (StrUtil.isNotEmpty(commonResponsePackage)) {
            imports.add(IMPORT_PREFIX + commonResponsePackage + ";");
        }
        String commonPageResponsePackage = generateConfig.getCommonPageResponsePackage();
        if (StrUtil.isNotEmpty(commonPageResponsePackage)) {
            imports.add(IMPORT_PREFIX + commonPageResponsePackage + ";");
        }
        //模型引入包
        String modelPackage = generateConfig.getModelPackage();
        if (StrUtil.isNotEmpty(modelPackage)) {
            String requestPackageName = generateConfig.getRequestPackageName();
            String responsePackageName = generateConfig.getResponsePackageName();
            if(StrUtil.isEmpty(requestPackageName) || StrUtil.isEmpty(responsePackageName)){
                imports.add(IMPORT_PREFIX + modelPackage + ".*;");
            }else{
                if(StrUtil.isNotEmpty(requestPackageName)){
                    imports.add(IMPORT_PREFIX + modelPackage + "." + requestPackageName + ".*;");
                }
                if(StrUtil.isNotEmpty(responsePackageName)){
                    imports.add(IMPORT_PREFIX + modelPackage + "." + responsePackageName + ".*;");
                }
            }
        }
    }

    //请求模型
    private void addRequestModel(Api api, ApiMethod apiMethod, Map<String, ApiModel> modelMap) {
        List<ApiModel> requestModel = api.getRequestModel();
        Set<String> refRequestModel = apiMethod.getRefRequestModel();
        if (CollectionUtil.isNotEmpty(refRequestModel)) {
            refRequestModel.forEach(reqModel -> {
                this.find(reqModel, modelMap, requestModel, false);
            });
        }
        //引入包,避免因为分包后相互依赖的情况
        if(CollectionUtil.isNotEmpty(requestModel)){
            String modelPackage = generateConfig.getModelPackage();
            String responsePackageName = generateConfig.getResponsePackageName();
            if(StrUtil.isNotEmpty(responsePackageName)){
                requestModel.forEach(reqModel -> reqModel.getImports().add("import " + modelPackage + "." + responsePackageName + ".*;"));
            }
        }
    }

    //返回模型
    private void addResponseModel(Api api, ApiMethod apiMethod, Map<String, ApiModel> modelMap) {
        List<ApiModel> responseModel = api.getResponseModel();
        Set<String> refResponseModel = apiMethod.getRefResponseModel();
        if (CollectionUtil.isNotEmpty(refResponseModel)) {
            refResponseModel.forEach(resModel -> {
                this.find(resModel, modelMap, responseModel, true);
            });
        }
        //引入包,避免因为分包后相互依赖的情况
        if(CollectionUtil.isNotEmpty(responseModel)){
            String modelPackage = generateConfig.getModelPackage();
            String requestPackageName = generateConfig.getRequestPackageName();
            if(StrUtil.isNotEmpty(requestPackageName)){
                responseModel.forEach(resModel -> resModel.getImports().add("import " + modelPackage + "." + requestPackageName + ".*;"));
            }
        }
    }

    //递归查询apimodel
    private void find(String apiModel, Map<String, ApiModel> modelMap, List<ApiModel> result, boolean removeCheck) {
        if (modelMap.containsKey(apiModel)) {
            ApiModel model = modelMap.get(apiModel);
            if (removeCheck) {
                this.removeAllCheck(model);
            }
            result.add(model);
            modelMap.remove(apiModel);
            Set<String> refModels = model.getRefModels();
            if (CollectionUtil.isNotEmpty(refModels)) {
                refModels.forEach(ref -> {
                    if (!ref.equals(apiModel)) {
                        find(ref, modelMap, result, removeCheck);
                    }
                });
            }
        }
    }

    //移除所有字典检查
    public void removeAllCheck(ApiModel apiModel) {
        List<ApiModelField> apiModelFields = apiModel.getApiModelFields();
        if (CollectionUtil.isNotEmpty(apiModelFields)) {
            apiModelFields.forEach(a -> {
                a.setRecursiveCheck(false);
                a.setListCheck(false);
                a.setNullCheck(false);
                a.setStringCheck(false);
            });
        }
    }

    //判断数组中所有元素是否都包含指定字符串
    private boolean isAllContains(List<String> list, String str) {
        String notContains = list.stream().filter(a -> !a.contains(str)).findFirst().orElse(null);
        if (StrUtil.isNotEmpty(notContains)) {
            log.info("{}未包含{}", notContains, str);
            return false;
        }
        return true;
    }
}
